/*******************************************************************************
 * Copyright (c) 2009 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Ali Ok (www.aliok.info) - initial API and implementation
 *******************************************************************************/
package org.eclipse.team.internal.search;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.search.ui.ISearchPageContainer;
import org.eclipse.search.ui.ISearchQuery;
import org.eclipse.search.ui.ISearchResult;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.history.IFileHistoryProvider;
import org.eclipse.ui.IWorkingSet;

final class SearchQuery implements ISearchQuery {

	/**
	 * Amount of work for whole search operation
	 */
	private static final int TOTAL_WORK_AMOUNT = 100;

	/**
	 * Amount of work for resource gathering
	 */
	private static final int RESOURCE_GATHERING_WORK_AMOUNT = 10;
	
	private String queryText;
	private ISearchPageContainer container;
	private SearchResult searchResult = new SearchResult(this);

	public SearchQuery(String text, ISearchPageContainer container) {
		this.queryText = text;
		this.container = container;
	}

	public boolean canRerun() {
		return false; /* for now */
	}

	public boolean canRunInBackground() {
		return true;
	}

	public String getLabel() {
		return "CVS Search result";
	}

	public ISearchResult getSearchResult() {
		return searchResult;
	}

	public IStatus run(final IProgressMonitor monitor)
			throws OperationCanceledException {
		//TODO: externalize 4 strings below
		monitor.beginTask("CVS Search", TOTAL_WORK_AMOUNT);
		SubProgressMonitor subProgressMonitor = new SubProgressMonitor(monitor, RESOURCE_GATHERING_WORK_AMOUNT, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
		subProgressMonitor.beginTask("Gathering Resources to Search", RESOURCE_GATHERING_WORK_AMOUNT);
		//not visible unless set explicitly
		subProgressMonitor.setTaskName("Gathering Resources to Search");
		
		int selectedScope = container.getSelectedScope();
		// gather IResources to search
		List<IResource> resourcesToSearch = gatherRoots(selectedScope);
		
		//half or resource gathering was collecting shared projects.
		subProgressMonitor.worked(RESOURCE_GATHERING_WORK_AMOUNT/2);
		
		//other half is calculating the resource count to search
		int resourceCount = calculateResourceCount(resourcesToSearch);
		
		//other half of resource gathering is done
		subProgressMonitor.worked(RESOURCE_GATHERING_WORK_AMOUNT/2);
		subProgressMonitor.done();
		

		//rest of the total work is searching
		IProgressMonitor searchProgressMonitor = new SubProgressMonitor(monitor, TOTAL_WORK_AMOUNT - RESOURCE_GATHERING_WORK_AMOUNT, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK);
		//TODO: externalize
		//search in each IResource in resourcesToSearch is 1 unit of work
		searchProgressMonitor.beginTask("Searching", resourceCount);
		
		performSearch(searchProgressMonitor, resourcesToSearch);
		
		return Status.OK_STATUS;
	}

	private List<IResource> gatherRoots(int selectedScope) {
		
		List<IResource> inScopeResources = new ArrayList<IResource>();
		
		switch (selectedScope) {
		case ISearchPageContainer.WORKSPACE_SCOPE: {
			IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
			IProject[] projects = root.getProjects();
			inScopeResources.addAll(Arrays.asList(projects));
			break;
		}
		case ISearchPageContainer.WORKING_SET_SCOPE: {
			IWorkingSet[] workingSets = container.getSelectedWorkingSets();
			for (IWorkingSet workingSet : workingSets) {
				for (IAdaptable adaptable : workingSet.getElements()) {
					IResource resource = (IResource) adaptable
							.getAdapter(IResource.class);
					inScopeResources.add(resource);
				}
			}
			break;
		}
		case ISearchPageContainer.SELECTED_PROJECTS_SCOPE: {
			String[] selectedProjectNames = container.getSelectedProjectNames();
			IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
			for (int i = 0; i < selectedProjectNames.length; i++) {
				IProject project = root.getProject(selectedProjectNames[i]);
				inScopeResources.add(project);
			}
			break;
		}
		case ISearchPageContainer.SELECTION_SCOPE: {
			ISelection selection = container.getSelection();
			if (selection == null
					|| !(selection instanceof IStructuredSelection)) {
				break;
			}
			
			IStructuredSelection structuredSelection = (IStructuredSelection) selection;
			
			for (Iterator iterator = structuredSelection.iterator(); iterator.hasNext();) {
				Object next = iterator.next();
				if (next instanceof IAdaptable) {
					IResource resource = (IResource) ((IAdaptable)next).getAdapter(IResource.class);
					inScopeResources.add(resource);
				}
		
			}
			
			break;
		}
		}
		
		return inScopeResources;
	}

	private void performSearch(IProgressMonitor searchProgressMonitor,
			List<IResource> resourcesToSearch) {
		// search
		for (Iterator<IResource> iterator = resourcesToSearch.iterator(); iterator
				.hasNext();) {
			IResource resource = iterator.next();
			try {
				IFileHistoryProvider fhp = getFileHistoryProviderForProject(resource
						.getProject());
				if(fhp != null){
					resource.accept(new SearchVisitor(fhp,
							searchProgressMonitor, searchResult, queryText));
				}
			} catch (CoreException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * This method try to visit each tree of resources, which roots are passed
	 * as parameter. If this method cannot visit particular subtree due to
	 * exception, then the subtree is skipped.
	 * 
	 * @param resourcesToSearch
	 *            - the list of roots to visit.
	 * @return the total number of resources
	 */
	private int calculateResourceCount(List<IResource> resourcesToSearch) {
		int resourceCount = 0;
		
		for (Iterator<IResource> iterator = resourcesToSearch.iterator(); iterator
				.hasNext();) {
			IResource resource = iterator.next();
			try {
				resourceCount += getResourceCount(resource);
			} catch (CoreException e) {
				// TODO proper handling
				e.printStackTrace();
			}
		}
		return resourceCount;
	}

	/**
	 * Calculates the resource count which are members of given resource. This
	 * method traverses the whole tree starting from the parameter.
	 * 
	 * @param resource
	 *            IResource to calculate the count of members(including itself)
	 *            <b>null</b> is not allowed.
	 * @return number of members(including given parameter)
	 * @throws CoreException
	 *             if this resource does not exist.
	 */
	private int getResourceCount(IResource resource) throws CoreException {
		ResourceCounter resourceCountVisitor = new ResourceCounter();
		resource.accept(resourceCountVisitor);
		return resourceCountVisitor.getResourceCount();
	}

	/**
	 * Returns the <code>IFileHistoryProvider</code> associated to given project.
	 * @param iResource <code>IResource</code> to get project's <code>IFileHistoryProvider</code> 
	 * @return null iResource is null or if project is not shared.
	 * @throws CoreException
	 */
	private IFileHistoryProvider getFileHistoryProviderForProject(
			IResource iResource) {
		if(iResource==null)
			return null;
		RepositoryProvider rp = RepositoryProvider.getProvider(iResource.getProject());
		if (rp == null) {
			// project is not shared
			return null;
		}
		
		return rp.getFileHistoryProvider();
	}

}